package com.zhijl.common.encryptor.asymmetric;

import com.zhijl.common.encryptor.bytetool.Hex;
import com.zhijl.common.encryptor.digest.SM3;
import com.zhijl.common.encryptor.digest.SM3Impl;
import com.zhijl.common.encryptor.symmetric.SM4Impl;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.openssl.PEMException;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;

import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPrivateKey;
import java.util.Arrays;
import java.util.Base64;
import java.util.Date;
import java.util.Enumeration;

/**
 * code from https://github.com/PopezLotado/SM2Java.git, 以bouncycastle代码为基础，进行了改造
 * <p>
 * 素数域上的椭圆曲线。 *
 * 参照标准《GM/T 0003.5-2012 SM2椭圆曲线公钥密码算法》
 * <p>
 * SM2公钥加密算法实现 包括 -签名,验签 -密钥交换 -公钥加密,私钥解密
 *
 * @author yang
 */
public class SM2Impl implements SM2 {

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    private static final int DIGEST_LENGTH = 32;
    public static ECCurve.Fp curve;
    private static int PUBLICKEY_X_LENGTH_IN_HEX = 64;

    /**
     * 保密局推荐的参数如下:
     */
    private static BigInteger p = new BigInteger(
            "FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFF".replaceAll(" ", ""), 16);
    private static BigInteger a = new BigInteger(
            "FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFC".replaceAll(" ", ""), 16);
    private static BigInteger b = new BigInteger(
            "28E9FA9E 9D9F5E34 4D5A9E4B CF6509A7 F39789F5 15AB8F92 DDBCBD41 4D940E93".replaceAll(" ", ""), 16);
    private static BigInteger gx = new BigInteger(
            "32C4AE2C 1F198119 5F990446 6A39C994 8FE30BBF F2660BE1 715A4589 334C74C7".replaceAll(" ", ""), 16);
    private static BigInteger gy = new BigInteger(
            "BC3736A2 F4F6779C 59BDCEE3 6B692153 D0A9877C C62A4740 02DF32E5 2139F0A0".replaceAll(" ", ""), 16);
    private static BigInteger n = new BigInteger(
            "FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF 7203DF6B 21C6052B 53BBF409 39D54123".replaceAll(" ", ""), 16);
    private static boolean useSelectedRandom = false;
    private static BigInteger fix_k;

    private static ECDomainParameters ecc_bc_spec;
    private static int w = (int) Math.ceil(n.bitLength() * 1.0 / 2) - 1;
    private static BigInteger _2w = new BigInteger("2").pow(w);
    private static SecureRandom random = new SecureRandom();

    private static ECPoint G;
    private boolean debug = false;

    public static ECPoint getG() {
        return G;
    }

    public SM2Impl() {
        curve = new ECCurve.Fp(p,
                // q
                a,
                b);
        G = curve.createPoint(gx, gy);
        ecc_bc_spec = new ECDomainParameters(curve, G, n);
    }

    /**
     * 是否开启测试模式
     *
     * @param debug true  开启
     *              false 关闭
     */
    public SM2Impl(boolean debug) {
        this();
        this.debug = debug;
    }

    // 截取超出32长度的字符数组
    private static byte[] transformByte(byte[] src) {
        byte[] dest = new byte[32];
        int srcPos = src.length - dest.length;
        System.arraycopy(src, srcPos, dest, 0, 32);
        return dest;
    }

    /**
     * 指定算法采用的随机数 测试专用
     *
     * @param nk 指定的随机数
     */
    public static void setRandom(BigInteger nk) {
        useSelectedRandom = true;
        fix_k = nk;
    }

    /**
     * 调整曲线参数为标准中的测试参数  测试专用
     */
    public static void changeParamToTest() {
        p = new BigInteger(
                "8542D69E4C044F18E8B92435BF6FF7DE457283915C45517D722EDB8B08F1DFC3", 16);
        a = new BigInteger(
                "787968B4FA32C3FD2417842E73BBFEFF2F3C848B6831D7E0EC65228B3937E498", 16);
        b = new BigInteger(
                "63E4C6D3B23B0C849CF84241484BFE48F61D59A5B16BA06E6E12D1DA27C5249A", 16);
        gx = new BigInteger(
                "421DEBD61B62EAB6746434EBC3CC315E32220B3BADD50BDC4C4E6C147FEDD43D", 16);
        gy = new BigInteger(
                "0680512BCBB42C07D47349D2153B70C4E5D7FDFCBFA36EA1A85841B9E46E09A2", 16);
        n = new BigInteger(
                "8542D69E4C044F18E8B92435BF6FF7DD297720630485628D5AE74EE7C32E79B7", 16);
    }

    /**
     * 调整曲线参数为标准中的推荐参数  测试专用
     */
    public static void changeParamToRecommand() {
        p = new BigInteger(
                "FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFF".replaceAll(" ", ""), 16);
        a = new BigInteger(
                "FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFC".replaceAll(" ", ""), 16);
        b = new BigInteger(
                "28E9FA9E 9D9F5E34 4D5A9E4B CF6509A7 F39789F5 15AB8F92 DDBCBD41 4D940E93".replaceAll(" ", ""), 16);
        gx = new BigInteger(
                "32C4AE2C 1F198119 5F990446 6A39C994 8FE30BBF F2660BE1 715A4589 334C74C7".replaceAll(" ", ""), 16);
        gy = new BigInteger(
                "BC3736A2 F4F6779C 59BDCEE3 6B692153 D0A9877C C62A4740 02DF32E5 2139F0A0".replaceAll(" ", ""), 16);
        n = new BigInteger(
                "FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF 7203DF6B 21C6052B 53BBF409 39D54123".replaceAll(" ", ""), 16);

    }

    /**
     * 随机数生成器
     *
     * @param max
     * @return
     */
    private static BigInteger random(BigInteger max) {

        BigInteger r = new BigInteger(256, random);

        while (r.compareTo(max) >= 0) {
            r = new BigInteger(128, random);
        }

        return r;
    }

    /**
     * 字节数组拼接
     *
     * @param params
     * @return
     */
    private static byte[] join(byte[]... params) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] res = null;
        try {
            for (int i = 0; i < params.length; i++) {
                baos.write(params[i]);
            }
            res = baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return res;
    }

    /**
     * sm3摘要
     *
     * @param params
     * @return
     */
    private static byte[] sm3hash(byte[]... params) {
        byte[] res = null;
        SM3 sm3Impl = new SM3Impl();

        res = sm3Impl.getHash(join(params));

        return res;
    }

    /**
     * 取得用户标识字节数组
     *
     * @param IDA        标识
     * @param aPublicKey 公钥
     * @return byte数组
     */
    private static byte[] ZA(String IDA, ECPoint aPublicKey) {
        byte[] idaBytes = new byte[0];
        try {
            idaBytes = IDA.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        int entlenA = idaBytes.length * 8;
        byte[] ENTLA = new byte[]{(byte) (entlenA & 0xFF00), (byte) (entlenA & 0x00FF)};

        byte[] pubXBytes = aPublicKey.getXCoord().toBigInteger().toByteArray();
        byte[] pubYBytes = aPublicKey.getYCoord().toBigInteger().toByteArray();
        byte[] aBytes = a.toByteArray();
        byte[] gyBytes = gy.toByteArray();

        return sm3hash(
                ENTLA,
                idaBytes,
                aBytes.length > 32 ? transformByte(aBytes) : aBytes,
                b.toByteArray(),
                gx.toByteArray(),
                gyBytes.length > 32 ? transformByte(gyBytes) : gyBytes,
                pubXBytes.length > 32 ? transformByte(pubXBytes) : pubXBytes,
                pubYBytes.length > 32 ? transformByte(pubYBytes) : pubYBytes);
    }

    /**
     * 密钥派生函数
     *
     * @param Z
     * @param klen 生成klen字节数长度的密钥
     * @return
     */
    private static byte[] KDF(byte[] Z, int klen) {
        int ct = 1;
        int end = (int) Math.ceil(klen * 1.0 / 32);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            for (int i = 1; i < end; i++) {
                baos.write(sm3hash(Z, toByteArray(ct)));
                ct++;
            }
            byte[] last = sm3hash(Z, toByteArray(ct));
            if (klen % 32 == 0) {
                baos.write(last);
            } else {
                baos.write(last, 0, klen % 32);
            }
            return baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 以16进制打印字节数组
     */
    public static void printHexString(byte[] b) {
        for (byte bb : b) {
            String hex = Integer.toHexString(bb & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            System.out.print("待处理的字符串：" + hex.toUpperCase());
        }
        System.out.println();
    }

    private static byte[] toByteArray(int i) {
        byte[] byteArray = new byte[4];
        byteArray[0] = (byte) (i >>> 24);
        byteArray[1] = (byte) ((i & 0xFFFFFF) >>> 16);
        byteArray[2] = (byte) ((i & 0xFFFF) >>> 8);
        byteArray[3] = (byte) (i & 0xFF);
        return byteArray;
    }

    public void setDebug(boolean b) {
        this.debug = b;
    }

    /**
     * 判断字节数组是否全0
     *
     * @param buffer
     * @return r
     */
    private boolean allZero(byte[] buffer) {
        for (byte num : buffer) {
            if (num != 0){
                return false;
            }
        }
        return true;
    }

    /**
     * 公钥加密
     *
     * @param inputBuffer 加密原文
     * @param publicKey   公钥
     * @return
     */
    @Override
    public byte[] encrypt(byte[] inputBuffer, ECPoint publicKey) {
        if (debug) {
            printHexString(inputBuffer);
        }

        byte[] C1Buffer;
        ECPoint kpb;
        byte[] t;
        do {
            /* 1 产生随机数k，k属于[1, n-1] 区间 */
            BigInteger k;
            if (useSelectedRandom) {
                k = SM2Impl.fix_k;
            } else {
                k = random(n);
            }
            if (debug) {
                System.out.println("fix_k: " + StringUtils.leftPad(k.toString(16), 64, "0"));
            }

            /* 2 计算椭圆曲线点C1 = [fix_k]G = (x1, y1) */
            ECPoint C1 = G.multiply(k);
            C1Buffer = C1.getEncoded(false);
            if (debug) {
                System.out.println("C1: " + Hex.encode(C1Buffer));
            }

            /*
             * 3 计算椭圆曲线点 S = [h]Pb
             */
            BigInteger h = ecc_bc_spec.getH();
            if (h != null) {
                ECPoint S = publicKey.multiply(h);
                if (S.isInfinity()) {
                    throw new IllegalStateException();
                }
            }

            /* 4 计算 [fix_k]PB = (x2, y2) */
            kpb = publicKey.multiply(k).normalize();
            if (debug) {
                System.out.println("kpb: " + Hex.encode(kpb.getEncoded()));
            }
            /* 5 计算 t = KDF(x2||y2, klen) */
            byte[] kpbBytes = kpb.getEncoded(false);
            byte[] kpbs = new byte[kpbBytes.length - 1];
            System.arraycopy(kpbBytes, 1, kpbs, 0, kpbs.length);
            System.out.println("kpbytes:\t" + Hex.encode(kpbBytes));
            System.out.println(kpbBytes.length + "," + kpbs.length);
            System.out.println("kpbs:\t\t" + Hex.encode(kpbs));
//            t = KDF(kpbBytes, inputBuffer.length*8);
            t = KDF(kpbs, inputBuffer.length * 8);
            if (debug) {
                System.out.println("klent:" + inputBuffer.length * 8);
                System.out.println("t: " + Hex.encode(t));
            }

            // DerivationFunction kdf = new KDF1BytesGenerator(new
            // ShortenedDigest(new SHA256Digest(), DIGEST_LENGTH));
            //
            // t = new byte[inputBuffer.length];
            // kdf.cache(new ISO18033KDFParameters(kpbBytes));
            // kdf.generateBytes(t, 0, t.length);
        } while (allZero(t));

        /* 6 计算C2=M^t */
        byte[] C2 = new byte[inputBuffer.length];
        for (int i = 0; i < inputBuffer.length; i++) {
            C2[i] = (byte) (inputBuffer[i] ^ t[i]);
        }

        if (debug) {
            System.out.println("c2: " + Hex.encode(C2));
        }

        /* 7 计算C3 = Hash(x2 || M || y2) */
        byte[] C3 = sm3hash(kpb.getXCoord().toBigInteger().toByteArray(), inputBuffer,
                kpb.getYCoord().toBigInteger().toByteArray());

        if (debug) {
            System.out.println("c3: " + Hex.encode(C3));
        }
        /* 8 输出 密文 C=C1 || C2 || C3 */
        byte[] encryptResult = new byte[C1Buffer.length + C2.length + C3.length];

        System.arraycopy(C1Buffer, 0, encryptResult, 0, C1Buffer.length);
        System.arraycopy(C2, 0, encryptResult, C1Buffer.length, C2.length);
        System.arraycopy(C3, 0, encryptResult, C1Buffer.length + C2.length, C3.length);

        if (debug) {
            System.out.print("密文-: " + Hex.encode(encryptResult));
        }

        return encryptResult;
    }

    /**
     * 私钥解密
     *
     * @param encryptData 密文数据字节数组
     * @param privateKey  解密私钥
     * @return 以utf-8编码的
     */
    @Override
    public String decrypt(byte[] encryptData, BigInteger privateKey) {

        if (debug) {
            System.out.println("encryptData length: " + encryptData.length);
        }

        byte[] C1Byte = new byte[65];
        System.arraycopy(encryptData, 0, C1Byte, 0, C1Byte.length);

        ECPoint C1 = curve.decodePoint(C1Byte).normalize();

        /*
         * 计算椭圆曲线点 S = [h]C1 是否为无穷点
         */
        BigInteger h = ecc_bc_spec.getH();
        if (h != null) {
            ECPoint S = C1.multiply(h);
            if (S.isInfinity()) {
                throw new IllegalStateException();
            }
        }
        /* 计算[dB]C1 = (x2, y2) */
        ECPoint dBC1 = C1.multiply(privateKey).normalize();
        if (debug) {
            System.out.println("db*c1: " + Hex.encode(dBC1.getEncoded(false)));
        }

        /* 计算t = KDF(x2 || y2, klen) */
        byte[] dBC1Bytes = dBC1.getEncoded(false);
        int klen = encryptData.length - 65 - DIGEST_LENGTH;

        //去除开头的04
        byte[] dbcs = new byte[dBC1Bytes.length - 1];
        System.arraycopy(dBC1Bytes, 1, dbcs, 0, dbcs.length);

//        byte[] t = KDF(dBC1Bytes, klen*8);
        byte[] t = KDF(dbcs, klen * 8);
        // DerivationFunction kdf = new KDF1BytesGenerator(new
        // ShortenedDigest(new SHA256Digest(), DIGEST_LENGTH));
        // if (debug)
        // System.out.println("klen = " + klen);
        // kdf.cache(new ISO18033KDFParameters(dBC1Bytes));
        // kdf.generateBytes(t, 0, t.length);

        if (allZero(t)) {
            System.err.println("all zero");
            throw new IllegalStateException();
        }

        /* 5 计算M'=C2^t */
        byte[] M = new byte[klen];
        for (int i = 0; i < M.length; i++) {
            M[i] = (byte) (encryptData[C1Byte.length + i] ^ t[i]);
        }
        if (debug) {
            printHexString(M);
        }

        /* 6 计算 u = Hash(x2 || M' || y2) 判断 u == C3是否成立 */
        byte[] C3 = new byte[DIGEST_LENGTH];

        if (debug) {
            try {
                System.out.println("M = " + new String(M, "UTF8"));
            } catch (UnsupportedEncodingException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
        }

        System.arraycopy(encryptData, encryptData.length - DIGEST_LENGTH, C3, 0, DIGEST_LENGTH);
        byte[] u = sm3hash(dBC1.getXCoord().toBigInteger().toByteArray(), M,
                dBC1.getYCoord().toBigInteger().toByteArray());
        if (Arrays.equals(u, C3)) {
            if (debug) {
                System.out.println("解密成功");
            }
            try {
                return new String(M, "UTF8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            return null;
        } else {
            if (debug) {
                System.out.print("u = ");
                printHexString(u);
                System.out.print("C3 = ");
                printHexString(C3);
                System.err.println("解密验证失败");
            }
            return null;
        }

    }

    /**
     * 判断是否在范围内
     *
     * @param param
     * @param min
     * @param max
     * @return
     */
    private boolean between(BigInteger param, BigInteger min, BigInteger max) {
        return param.compareTo(min) >= 0 && param.compareTo(max) < 0;
    }

    /**
     * 判断生成的公钥是否合法
     *
     * @param publicKey
     * @return
     */
    private boolean checkPublicKey(ECPoint publicKey) {

        if (!publicKey.isInfinity()) {

            BigInteger x = publicKey.getXCoord().toBigInteger();
            BigInteger y = publicKey.getYCoord().toBigInteger();

            if (between(x, new BigInteger("0"), p) && between(y, new BigInteger("0"), p)) {

                BigInteger xResult = x.pow(3).add(a.multiply(x)).add(b).mod(p);

                if (debug) {
                    System.out.println("xResult: " + xResult.toString());
                }

                BigInteger yResult = y.pow(2).mod(p);

                if (debug) {
                    System.out.println("yResult: " + yResult.toString());
                }

                if (yResult.equals(xResult) && publicKey.multiply(n).isInfinity()) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 生成密钥对
     *
     * @return
     */
    @Override
    public SM2KeyPair generateValidKeyPair() {
        SM2KeyPair keyPair = null;

        while (keyPair == null) {
            keyPair = generateKeyPair();
        }

        return keyPair;
    }

    @Override
    public SM2SelfedCert genSelfedCertWithPassword(String password) {

        SM2SelfedCert sm2SelfedCert = genSelfedCertObj();
        sm2SelfedCert.encryptSKBySM4(password);
        return sm2SelfedCert;
    }

    /**
     * 生成密钥对
     *
     * @return
     */
    private SM2KeyPair generateKeyPair() {

        BigInteger d = random(n.subtract(new BigInteger("1")));

        SM2KeyPair keyPair = new SM2KeyPair(G.multiply(d).normalize(), d);

        if (checkPublicKey(keyPair.getPublicKey())) {
            if (debug) {
                System.out.println("generate Key successfully");
            }
            return keyPair;
        } else {
            if (debug) {
                System.err.println("generate Key failed");
            }
            return null;
        }
    }

    /**
     * 签名
     *
     * @param M       签名信息
     * @param IDA     签名方唯一标识 walletaddr。
     * @param keyPair 签名方密钥对
     * @return 签名
     */
    @Override
    public byte[] sign(SM2KeyPair keyPair, byte[] M, String IDA) {
        byte[] ZA = ZA(IDA, keyPair.getPublicKey());
        byte[] M_ = join(ZA, M);
        BigInteger e = new BigInteger(1, sm3hash(M_));

        BigInteger k;
        BigInteger r;
        do {
            if (useSelectedRandom) {
                k = SM2Impl.fix_k;
            } else {
                k = random(n);
            }
            ECPoint p1 = G.multiply(k).normalize();
            BigInteger x1 = p1.getXCoord().toBigInteger();
            r = e.add(x1);
            r = r.mod(n);
        } while (r.equals(BigInteger.ZERO) || r.add(k).equals(n));

        BigInteger s = ((keyPair.getPrivateKey().add(BigInteger.ONE).modInverse(n))
                .multiply((k.subtract(r.multiply(keyPair.getPrivateKey()))).mod(n))).mod(n);
        Signature signature = new Signature(r, s);
        return signature.encodeASN1();
    }

    @Override
    public String signWithPassword(String ePriKey, String password, String msg) {
        SM2SelfedCert sm2SelfedCert = new SM2SelfedCert(ePriKey);
        sm2SelfedCert.decryptSKBySM4(password);

        SM2KeyPair sm2KeyPair = sk2PriKeyObject(sm2SelfedCert.getSkCert());

        byte[] sign = sign(sm2KeyPair, msg.getBytes(StandardCharsets.UTF_8), DEFAULT_IDA);
        return Hex.encode(sign);
    }


    /**
     * 验签
     *
     * @param M          记性sign的原始data信息
     * @param signdata   符合ASN.1编码格式的签名数据的字节数组
     * @param IDA        签名方唯一标识
     * @param aPublicKey 签名方公钥
     * @return true or false
     * @throws RuntimeException 当解码失败或其他异常情况抛出
     */
    @Override
    public boolean verify(byte[] M, ECPoint aPublicKey, byte[] signdata, String IDA) {
        Signature signature = new Signature(signdata);
        if (!between(signature.r, BigInteger.ONE, n)) {
            return false;
        }
        if (!between(signature.s, BigInteger.ONE, n)) {
            return false;
        }

        byte[] M_ = join(ZA(IDA, aPublicKey), M);
        BigInteger e = new BigInteger(1, sm3hash(M_));
        BigInteger t = signature.r.add(signature.s).mod(n);

        if (t.equals(BigInteger.ZERO)) {
            return false;
        }

        ECPoint p1 = G.multiply(signature.s).normalize();
        ECPoint p2 = aPublicKey.multiply(t).normalize();
        BigInteger x1 = p1.add(p2).normalize().getXCoord().toBigInteger();
        BigInteger R = e.add(x1).mod(n);

        return R.equals(signature.r);
    }

    @Override
    public boolean verifyByPemCert(String msg, String PemCert, String signData, String IDA) {
        String pubKey = pem2PubKey(PemCert);
        ECPoint ecPoint = decodePublicKey(pubKey);
        return verify(
                msg.getBytes(StandardCharsets.UTF_8),
                ecPoint,
                Hex.decode(signData),
                DEFAULT_IDA
        );
    }

    public SM2KeyPair genKeypairByPrivateKey(BigInteger prikey) {
        return new SM2KeyPair(G.multiply(prikey).normalize(), prikey);
    }

    public SM2KeyPair genKeypairByPrivateKey(byte[] prikey) {
        BigInteger priKeyBigInt = new BigInteger(Hex.encode(prikey), 16);
        return new SM2KeyPair(G.multiply(priKeyBigInt).normalize(), priKeyBigInt);
    }

    //公钥的字符串形式。04 || x的hex形式 || y的hex形式
    @Override
    public String encodePublicKey(ECPoint publicKey) {
        if (publicKey == null) {
            return null;
        }
        BigInteger x = publicKey.getXCoord().toBigInteger();
        BigInteger y = publicKey.getYCoord().toBigInteger();

        return "04" + StringUtils.leftPad(x.toString(16), 64, "0") + StringUtils.leftPad(y.toString(16), 64, "0");
    }

    @Override
    public ECPoint decodePublicKey(String encodedKey) {
        if (StringUtils.isBlank(encodedKey) || !encodedKey.startsWith("04")) {
            return null;
        }

        encodedKey = encodedKey.substring(2);
        String xs = encodedKey.substring(0, encodedKey.length() / 2);
        String ys = encodedKey.substring(encodedKey.length() / 2);

        BigInteger px = new BigInteger(xs, 16);
        BigInteger py = new BigInteger(ys, 16);

        return curve.createPoint(px, py);
    }

    //私钥的字符串形式，直接hex编码
    @Override
    public String encodePrivateKey(BigInteger privateKey) {
        return Hex.encode(privateKey.toByteArray());
    }

    @Override
    public BigInteger decodePrivateKey(String encodedKey) {
        return new BigInteger(Hex.decode(encodedKey));
    }

    /**
     * 传输实体类
     */
    public static class TransportEntity implements Serializable {
        final byte[] R; //R点
        final byte[] S; //验证S
        final byte[] Z; //用户标识
        final byte[] K; //公钥

        public TransportEntity(byte[] r, byte[] s, byte[] z, ECPoint pKey) {
            R = r;
            S = s;
            Z = z;
            K = pKey.getEncoded(false);
        }
    }

    /**
     * 密钥协商辅助类
     */
    public static class KeyExchange {
        BigInteger rA;
        ECPoint RA;
        ECPoint V;
        byte[] Z;
        byte[] key;

        String ID;
        SM2KeyPair keyPair;

        public KeyExchange(String ID, SM2KeyPair keyPair) {
            this.ID = ID;
            this.keyPair = keyPair;
            this.Z = ZA(ID, keyPair.getPublicKey());
        }

        /**
         * 密钥协商发起第一步
         *
         * @return
         */
        public TransportEntity keyExchange_1() {
            rA = random(n);
            // rA=new BigInteger("83A2C9C8 B96E5AF7 0BD480B4 72409A9A 327257F1
            // EBB73F5B 073354B2 48668563".replace(" ", ""),16);
            RA = G.multiply(rA).normalize();
            return new TransportEntity(RA.getEncoded(false), null, Z, keyPair.getPublicKey());
        }

        /**
         * 密钥协商响应方
         *
         * @param entity 传输实体
         * @return
         */
        public TransportEntity keyExchange_2(TransportEntity entity) {
            BigInteger rB = random(n);
            // BigInteger rB=new BigInteger("33FE2194 0342161C 55619C4A 0C060293
            // D543C80A F19748CE 176D8347 7DE71C80".replace(" ", ""),16);
            ECPoint RB = G.multiply(rB).normalize();

            this.rA = rB;
            this.RA = RB;

            BigInteger x2 = RB.getXCoord().toBigInteger();
            x2 = _2w.add(x2.and(_2w.subtract(BigInteger.ONE)));

            BigInteger tB = keyPair.getPrivateKey().add(x2.multiply(rB)).mod(n);
            ECPoint RA = curve.decodePoint(entity.R).normalize();

            BigInteger x1 = RA.getXCoord().toBigInteger();
            x1 = _2w.add(x1.and(_2w.subtract(BigInteger.ONE)));

            ECPoint aPublicKey = curve.decodePoint(entity.K).normalize();
            ECPoint temp = aPublicKey.add(RA.multiply(x1).normalize()).normalize();
            ECPoint V = temp.multiply(ecc_bc_spec.getH().multiply(tB)).normalize();
            if (V.isInfinity()) {
                throw new IllegalStateException();
            }
            this.V = V;

            byte[] xV = V.getXCoord().toBigInteger().toByteArray();
            byte[] yV = V.getYCoord().toBigInteger().toByteArray();
            byte[] KB = KDF(join(xV, yV, entity.Z, this.Z), 16);
            key = KB;
            System.out.print("协商得B密钥:");
            printHexString(KB);
            byte[] sB = sm3hash(new byte[]{0x02}, yV,
                    sm3hash(xV, entity.Z, this.Z, RA.getXCoord().toBigInteger().toByteArray(),
                            RA.getYCoord().toBigInteger().toByteArray(), RB.getXCoord().toBigInteger().toByteArray(),
                            RB.getYCoord().toBigInteger().toByteArray()));
            return new TransportEntity(RB.getEncoded(false), sB, this.Z, keyPair.getPublicKey());
        }

        /**
         * 密钥协商发起方第二步
         *
         * @param entity 传输实体
         */
        public TransportEntity keyExchange_3(TransportEntity entity) {
            BigInteger x1 = RA.getXCoord().toBigInteger();
            x1 = _2w.add(x1.and(_2w.subtract(BigInteger.ONE)));

            BigInteger tA = keyPair.getPrivateKey().add(x1.multiply(rA)).mod(n);
            ECPoint RB = curve.decodePoint(entity.R).normalize();

            BigInteger x2 = RB.getXCoord().toBigInteger();
            x2 = _2w.add(x2.and(_2w.subtract(BigInteger.ONE)));

            ECPoint bPublicKey = curve.decodePoint(entity.K).normalize();
            ECPoint temp = bPublicKey.add(RB.multiply(x2).normalize()).normalize();
            ECPoint U = temp.multiply(ecc_bc_spec.getH().multiply(tA)).normalize();
            if (U.isInfinity()) {
                throw new IllegalStateException();
            }
            this.V = U;

            byte[] xU = U.getXCoord().toBigInteger().toByteArray();
            byte[] yU = U.getYCoord().toBigInteger().toByteArray();
            byte[] KA = KDF(join(xU, yU,
                    this.Z, entity.Z), 16);
            key = KA;
            System.out.print("协商得A密钥:");
            printHexString(KA);
            byte[] s1 = sm3hash(new byte[]{0x02}, yU,
                    sm3hash(xU, this.Z, entity.Z, RA.getXCoord().toBigInteger().toByteArray(),
                            RA.getYCoord().toBigInteger().toByteArray(), RB.getXCoord().toBigInteger().toByteArray(),
                            RB.getYCoord().toBigInteger().toByteArray()));
            if (Arrays.equals(entity.S, s1)) {
                System.out.println("B->A 密钥确认成功");
            } else {
                System.out.println("B->A 密钥确认失败");
            }
            byte[] sA = sm3hash(new byte[]{0x03}, yU,
                    sm3hash(xU, this.Z, entity.Z, RA.getXCoord().toBigInteger().toByteArray(),
                            RA.getYCoord().toBigInteger().toByteArray(), RB.getXCoord().toBigInteger().toByteArray(),
                            RB.getYCoord().toBigInteger().toByteArray()));

            return new TransportEntity(RA.getEncoded(false), sA, this.Z, keyPair.getPublicKey());
        }

        /**
         * 密钥确认最后一步
         *
         * @param entity 传输实体
         */
        public void keyExchange_4(TransportEntity entity) {
            byte[] xV = V.getXCoord().toBigInteger().toByteArray();
            byte[] yV = V.getYCoord().toBigInteger().toByteArray();
            ECPoint RA = curve.decodePoint(entity.R).normalize();
            byte[] s2 = sm3hash(new byte[]{0x03}, yV,
                    sm3hash(xV, entity.Z, this.Z, RA.getXCoord().toBigInteger().toByteArray(),
                            RA.getYCoord().toBigInteger().toByteArray(), this.RA.getXCoord().toBigInteger().toByteArray(),
                            this.RA.getYCoord().toBigInteger().toByteArray()));
            if (Arrays.equals(entity.S, s2)) {
                System.out.println("A->B 密钥确认成功");
            } else {
                System.out.println("A->B 密钥确认失败");
            }
        }
    }

    public static class Signature {
        BigInteger r;
        BigInteger s;

        public Signature(BigInteger r, BigInteger s) {
            this.r = r;
            this.s = s;
        }

        /**
         * 通过签名数据构建对象
         *
         * @param asn 符合ASN.1编码的字节数组(签名数据)
         * @throws RuntimeException
         */
        public Signature(byte[] asn) {
            try {
                ASN1InputStream ais = new ASN1InputStream(new ByteArrayInputStream(asn));
                while (ais.available() > 0) {
                    ASN1Primitive obj = ais.readObject();
                    Enumeration e = ((ASN1Sequence) obj).getObjects();
                    int count = 0;
                    while (e.hasMoreElements()) {
                        Object o = e.nextElement();
                        if (o instanceof ASN1Integer) {
                            ASN1Integer asn1Integer = (ASN1Integer) o;
                            byte[] temp = asn1Integer.getValue().toByteArray();
                            if (count == 0) {
                                this.r = new BigInteger(Hex.encode(temp.length > 32 ? transformByte(temp) : temp), 16);
                            } else if (count == 1) {
                                this.s = new BigInteger(Hex.encode(temp.length > 32 ? transformByte(temp) : temp), 16);
                            }
                            count++;
                        }
                    }
                }
                ais.close();
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage());
            }
        }

        public Signature() {
        }

        @Deprecated
        //根据tostring的结果反向构造Signature
        public static Signature genByHex(String hex) {
            BigInteger r = new BigInteger(hex.substring(0, 64), 16);
            BigInteger s = new BigInteger(hex.substring(64, 128), 16);
            return new Signature(r, s);
        }

        @Deprecated
        //根据tostring的结果反向构造Signature
        public static Signature genByBase64(String base) {
            byte[] bs = Base64.getDecoder().decode(base);

            byte[] rs = new byte[bs.length / 2];
            byte[] ss = new byte[bs.length / 2];

            System.arraycopy(bs, 0, rs, 0, rs.length);
            System.arraycopy(bs, rs.length, ss, 0, ss.length);

            BigInteger r = new BigInteger(Hex.encode(rs), 16);
            BigInteger s = new BigInteger(Hex.encode(ss), 16);
            return new Signature(r, s);
        }

        /**
         * 签名对象>>ASN.1编码的字节数组
         *
         * @return
         */
        public byte[] encodeASN1() {

            ByteArrayOutputStream ois = new ByteArrayOutputStream();
            try {
                DERSequenceGenerator seq = new DERSequenceGenerator(ois);

                seq.addObject(new ASN1Integer(this.r));
                seq.addObject(new ASN1Integer(this.s));

                seq.close();
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
            return ois.toByteArray();
        }

        public String getR() {
            return Hex.encode(r.toByteArray());
//            return StringUtils.leftPad(r.toString(16), 64, "0");
        }

        public String getS() {
            return Hex.encode(s.toByteArray());
//            return StringUtils.leftPad(s.toString(16), 64, "0");
        }

        @Override
        public String toString() {
            System.out.println("sign r: " + StringUtils.leftPad(r.toString(16), 64, "0")
                    + "\nsign s: " + StringUtils.leftPad(s.toString(16), 64, "0"));

            return StringUtils.leftPad(r.toString(16), 64, "0")
                    + StringUtils.leftPad(s.toString(16), 64, "0");
        }

        @Deprecated
        public String toBase64() {
            String hex = StringUtils.leftPad(r.toString(16), 64, "0")
                    + StringUtils.leftPad(s.toString(16), 64, "0");
            byte[] bytes = Hex.decode(hex);
            return Base64.getEncoder().encodeToString(bytes);
        }
    }


    /**
     * pem证书获取公钥
     *
     * @param pemStr pem字符串
     * @return hex string 公钥
     * @throws RuntimeException 参数为null或empty/证书实例获取异常/证书获取失败
     */
    @Override
    public String pem2PubKey(String pemStr) {

        CertificateFactory certFactory;

        Certificate certificate = null;
        BufferedInputStream pem = null;
        try {
            pem = new BufferedInputStream(new ByteArrayInputStream(pemStr.trim().getBytes()));
            certFactory = CertificateFactory.getInstance("X.509", "BC");
            certificate = certFactory.generateCertificate(pem);
        } catch (Exception e) {
            throw new RuntimeException("获取证书实例出现异常", e);
        } finally {
            try {
                if (null != pem) {
                    pem.close();
                }
            } catch (IOException e) {
                throw new RuntimeException("关闭pem流出现异常", e);
            }
        }
        if (null == certificate) {
            throw new RuntimeException("证书获取失败");
        }
        String sc = Hex.encode(certificate.getPublicKey().getEncoded());
        String pubkeyStr = StringUtils.join("04", sc.substring(sc.length() - 128));

        return pubkeyStr;
    }

    /**
     * sk证书获取公私钥
     *
     * @param skStr sk文件字符串
     * @return json hex pubKey&priKey string
     * @throws RuntimeException 参数为null或empty/证书解析错误/生成priKey异常
     */
    @Override
    public String sk2PriKey(String skStr) {

        SM2KeyPair sm2KeyPair = sk2PriKeyObject(skStr);
        String keyStr = StringUtils.join("{\"priKey\":\"", Hex.encode(sm2KeyPair.getPrivateKey().toByteArray()), "\",",
                "\"pubKey\":\"", Hex.encode(sm2KeyPair.getPublicKey().getEncoded()), "\"}"
        );

        return keyStr;
    }

    /**
     * sk证书获取公私钥
     *
     * @param skStr sk文件字符串
     * @return json hex pubKey&priKey string
     * @throws RuntimeException 参数为null或empty/证书解析错误/生成priKey异常
     */
    private SM2KeyPair sk2PriKeyObject(String skStr) {

        if (StringUtils.isBlank(skStr)) {
            throw new RuntimeException("skStr is null or empty");
        }

        byte[] skByteArr = skStr.trim().getBytes(StandardCharsets.UTF_8);

        if (ArrayUtils.isEmpty(skByteArr)) {
            throw new RuntimeException("skStr concert byte[] err");
        }

        final Reader pemReader = new StringReader(new String(skByteArr, StandardCharsets.UTF_8));

        PrivateKeyInfo pemPair;
        try (PEMParser pemParser = new PEMParser(pemReader)) {
            pemPair = (PrivateKeyInfo) pemParser.readObject();
        } catch (IOException e) {
            throw new RuntimeException("证书流解析异常", e);
        }

        PrivateKey privateKey;
        try {
            privateKey = new JcaPEMKeyConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getPrivateKey(pemPair);
        } catch (PEMException e) {
            throw new RuntimeException("生成privateKey异常异常", e);
        }

        ECPrivateKey ecPrivateKey = (ECPrivateKey) privateKey;
        SM2KeyPair sm2KeyPair = this.genKeypairByPrivateKey(ecPrivateKey.getS());

        return sm2KeyPair;
    }

    /**
     * 解密sm4加密后的sk文件内容
     *
     * @param skStr  base64编码格式的sm4加密内容
     * @param keyArr sm4加密的key
     * @return json hex pubKey&priKey string
     * @throws RuntimeException 参数为null或empty/解密失败/流关闭错误
     *                          sk2PriKey(string)函数发生的异常
     */
    private byte[] sk2PriKey(String skStr, byte[] keyArr) {

        if (StringUtils.isBlank(skStr)) {
            throw new RuntimeException("skStr is null or empty");
        }

        if (ArrayUtils.isEmpty(keyArr)) {
            throw new RuntimeException("keyArr is null or empty");
        }

        SM4Impl sm4 = new SM4Impl();

        ByteArrayInputStream inputStream = new ByteArrayInputStream(skStr.getBytes(StandardCharsets.UTF_8));
        ByteArrayOutputStream outStream = new ByteArrayOutputStream(200);

        try {
            sm4.decryptStream(inputStream, outStream, keyArr);
        } catch (IOException e) {
            throw new RuntimeException("sm4解密错误");
        } finally {

            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                throw new RuntimeException("inputStream close() fail", e);
            }

            try {
                if (outStream != null) {
                    outStream.close();
                }
            } catch (IOException e) {
                throw new RuntimeException("outStream close() fail", e);
            }
        }


        System.out.println("============" + new String(outStream.toByteArray(), StandardCharsets.UTF_8));

        final Reader pemReader = new StringReader(new String(outStream.toByteArray(), StandardCharsets.UTF_8));

        PrivateKeyInfo skPair;
        try (PEMParser pemParser = new PEMParser(pemReader)) {
            skPair = (PrivateKeyInfo) pemParser.readObject();
        } catch (IOException e) {
            throw new RuntimeException("证书流解析异常", e);
        }

        PrivateKey privateKey;
        try {
            privateKey = new JcaPEMKeyConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getPrivateKey(skPair);
        } catch (PEMException e) {
            throw new RuntimeException("生成privateKey异常异常", e);
        }

        return privateKey.getEncoded();

    }


    /**
     * 默认CN名称
     */
    private final static String DEFAULT_CN_NAME = "zjlCA";

    /**
     * 生成sk和pem证书字符串
     *
     * @return json base64 cert String
     * @throws RuntimeException 证书相关异常...,异常发生表示生成失败
     */
    @Override
    public String genSelfedCert() {
        return genSelfedCert(DEFAULT_CN_NAME);

    }

    @Override
    public String genSelfedCert(String CNName) {
        return genSelfedCertObj(CNName).toJsonString();
    }

    public SM2SelfedCert genSelfedCertObj() {
        return genSelfedCertObj(DEFAULT_CN_NAME);
    }

    /**
     * 生成sk和pem证书字符串
     *
     * @param CNName CN名称
     * @return json base64 cert String
     * @throws RuntimeException 证书相关异常...,异常发生表示生成失败
     */
    private SM2SelfedCert genSelfedCertObj(String CNName) {

        ECNamedCurveParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec("sm2p256v1");
        KeyPairGenerator keyPairGenerator;

        try {
            keyPairGenerator = KeyPairGenerator.getInstance("ECDSA", "BC");
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }

        try {
            keyPairGenerator.initialize(parameterSpec);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }

        KeyPair keyPair = keyPairGenerator.generateKeyPair();


        X500Name subjectDN = new X500Name(StringUtils.join("CN=", CNName, ",OU=zjl,O=zjl.com,L=BJ,C=CN"));
        BigInteger serialNumber = BigInteger.valueOf(System.nanoTime());
        Date startDate = new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000);
        Date endDate = new Date(System.currentTimeMillis() + 365 * 24 * 60 * 60 * 1000);
        SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());

        X509v3CertificateBuilder builder = new X509v3CertificateBuilder(subjectDN, serialNumber, startDate,
                endDate, subjectDN, subPubKeyInfo);

        ContentSigner signer;
        try {
            signer = new JcaContentSignerBuilder("sm3WithSM2").setProvider("BC").build(keyPair.getPrivate());
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }

        X509CertificateHolder holder = builder.build(signer);
        X509Certificate certificate;
        try {
            certificate = new JcaX509CertificateConverter().setProvider("BC").getCertificate(holder);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }

        String preCert = "-----BEGIN CERTIFICATE-----\n";
        String presk = "-----BEGIN PRIVATE KEY-----\n";

        String endCert = "\n-----END CERTIFICATE-----";
        String endsk = "\n-----END PRIVATE KEY-----";


        String pemStr;
        try {
            pemStr = StringUtils.join(
                    preCert,
                    Base64.getEncoder().encodeToString(certificate.getEncoded()),
                    endCert
            );
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }

        String skStr = StringUtils.join(
                presk,
                Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded()),
                endsk
        );

        return new SM2SelfedCert(skStr, pemStr);
    }


        /*@Override
    public Signature decodeASN1(byte[] asn){
        Signature signature = new Signature();
        try{
            ASN1InputStream ais = new ASN1InputStream(new ByteArrayInputStream(asn));
            while (ais.available() > 0) {
                ASN1Primitive obj = ais.readObject();
                Enumeration e = ((ASN1Sequence) obj).getObjects();
                int count =0;
                while (e.hasMoreElements()) {
                    Object o = e.nextElement();
                    if (o instanceof ASN1Integer) {
                        ASN1Integer asn1Integer = (ASN1Integer) o;
                        byte[] temp = asn1Integer.getValue().toByteArray();
                        if (count == 0){
                            signature.r = new BigInteger(Hex.encode(temp.length >32?transformByte(temp):temp),16);
                        }else{
                            signature.s = new BigInteger(Hex.encode(temp.length >32?transformByte(temp):temp),16);
                        }
                        count++;
                    }
                }
            }
            ais.close();
        } catch(Exception e){
            System.out.println(e.getMessage());
        }
        return signature;
    }

    @Override
    public byte[] encodeASN1(BigInteger r,BigInteger s) {
        ByteArrayOutputStream ois = new ByteArrayOutputStream();
        try{
            DERSequenceGenerator seq = new DERSequenceGenerator(ois);

            seq.addObject(new ASN1Integer(r));
            seq.addObject(new ASN1Integer(s));

            seq.close();
        } catch(Exception e){
            System.out.println(e.getMessage());
        }
        return ois.toByteArray();
    }*/
}
